# @Version        : 1.0
# @Update Time    : 2025/8/25 23:13
# @File           : chart_builders.py
# @IDE            : PyCharm
# @Desc           : 文件描述信息
from typing import Sequence, List, Dict, Tuple, Optional
from pyecharts import options as opts
from pyecharts.charts import Bar, Pie, Boxplot, Bar3D, Map
from pyecharts.commons.utils import JsCode
from pyecharts.globals import ThemeType


def init_opts(theme: ThemeType = ThemeType.LIGHT, width: str = "100%", height: str = "360px") -> opts.InitOpts:
    return opts.InitOpts(theme=theme, width=width, height=height)


def to_percent_pairs(a: Sequence[float], b: Sequence[float]) -> Tuple[List[Dict], List[Dict]]:
    res_a, res_b = [], []
    for x, y in zip(a, b):
        s = (x or 0) + (y or 0)
        pa = (x / s) if s else 0
        pb = (y / s) if s else 0
        res_a.append({"value": x, "percent": pa})
        res_b.append({"value": y, "percent": pb})
    return res_a, res_b


def build_stacked_percent_bar(
    x_axis: Sequence,
    a: Sequence[float],
    b: Sequence[float],
    title: str = "stack_bar",
    theme: ThemeType = ThemeType.LIGHT,
) -> Bar:
    sa, sb = to_percent_pairs(a, b)
    return (
        Bar(init_opts=init_opts(theme))
        .add_xaxis(list(x_axis))
        .add_yaxis("product1", sa, stack="stack1", category_gap="50%")
        .add_yaxis("product2", sb, stack="stack1", category_gap="50%")
        .set_series_opts(
            label_opts=opts.LabelOpts(
                position="right", formatter=JsCode("function(x){return Number(x.data.percent * 100).toFixed(0) + '%';}")
            )
        )
        .set_global_opts(title_opts=opts.TitleOpts(title=title))
    )


def build_rose_pie(
    data: Optional[Sequence[Tuple[str, float]]] = None,
    title: str = "玫瑰图",
    theme: ThemeType = ThemeType.LIGHT,
) -> Pie:
    from pyecharts.faker import Faker

    pairs = data or [list(z) for z in zip(Faker.choose(), Faker.values())]
    return (
        Pie(init_opts=init_opts(theme))
        .add(
            "",
            pairs,
            radius=["30%", "75%"],
            center=["45%", "50%"],
            rosetype="radius",
            label_opts=opts.LabelOpts(is_show=False),
        )
        .set_global_opts(title_opts=opts.TitleOpts(title=title))
    )


def build_boxplot(
    labels: Sequence[str],
    data_groups: Sequence[Sequence[Sequence[float]]],  # 每个系列一组二维数组
    series_names: Sequence[str],
    title: str = "BoxPlot-基本示例",
    theme: ThemeType = ThemeType.LIGHT,
) -> Boxplot:
    c = Boxplot(init_opts=init_opts(theme))
    c.add_xaxis(list(labels))
    for name, raw in zip(series_names, data_groups):
        c.add_yaxis(name, c.prepare_data(raw))
    c.set_global_opts(title_opts=opts.TitleOpts(title=title))
    return c


def build_bar3d_stack(
    x_data: Sequence[int],
    y_data: Sequence[int],
    stacks: int = 10,
    title: str = "Bar3D-堆叠柱状图示例",
    theme: ThemeType = ThemeType.LIGHT,
) -> Bar3D:
    import random

    def gen():
        data = []
        for j in x_data:
            for k in y_data:
                v = random.randint(0, 9)
                data.append([j, k, v * 2 + 4])
        return data

    bar3d = Bar3D(init_opts=init_opts(theme, height="480px"))
    for _ in range(stacks):
        bar3d.add(
            "",
            gen(),
            shading="lambert",
            xaxis3d_opts=opts.Axis3DOpts(data=list(x_data), type_="value"),
            yaxis3d_opts=opts.Axis3DOpts(data=list(y_data), type_="value"),
            zaxis3d_opts=opts.Axis3DOpts(type_="value"),
        )
    bar3d.set_global_opts(title_opts=opts.TitleOpts(title=title))
    bar3d.set_series_opts(stack="stack")
    return bar3d


def build_map_china(
    pairs: Optional[Sequence[Tuple[str, float]]] = None,
    title: str = "Map-基本示例",
    theme: ThemeType = ThemeType.LIGHT,
) -> Map:
    from pyecharts.faker import Faker

    data = pairs or [list(z) for z in zip(Faker.provinces, Faker.values())]
    return (
        Map(init_opts=init_opts(theme))
        .add("商家A", data, "china")
        .set_global_opts(title_opts=opts.TitleOpts(title=title))
    )
